/*
	This file is part of Warzone 2100.
	Copyright (C) 1999-2004  Eidos Interactive
	Copyright (C) 2005-2007  Warzone Resurrection Project

	Warzone 2100 is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	Warzone 2100 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Warzone 2100; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

/*
 * Frame.c
 *
 * Initialisation and shutdown for the framework library.
 *
 * Includes a basic windows message loop.
 *
 */
#include "frame.h"
#include "file.h"

#include <SDL.h>
#include <physfs.h>

#include "frameint.h"
#include "frameresource.h"
#include "input.h"
#include "SDL_framerate.h"

#include "cursors.h"

static const enum CURSOR_TYPE cursor_type =
#ifdef __APPLE__
	CURSOR_16;
#else
	CURSOR_32;
#endif

/* Linux specific stuff */

static CURSOR currentCursor = ~(CURSOR)0;
static SDL_Cursor* aCursors[CURSOR_MAX];

FOCUS_STATE focusState = FOCUS_IN;

bool selfTest = false;

/************************************************************************************
 *
 *	Player globals
 */

UDWORD		selectedPlayer = 0; 	/**< Current player */


/************************************************************************************
 *
 * Alex's frame rate stuff
 */

/* Over how many seconds is the average required? */
#define	TIMESPAN	5

/* Initial filler value for the averages - arbitrary */
#define  IN_A_FRAME 70

/* Global variables for the frame rate stuff */
static Uint32	FrameCounts[TIMESPAN] = { 0 };
static Uint32	FrameIndex = 0;
static Uint64	curFrames = 0; // Number of frames elapsed since start
static Uint64	lastFrames = 0;
static Uint32	curTicks = 0; // Number of ticks since execution started
static Uint32	lastTicks = 0;
static FPSmanager wzFPSmanager;
static BOOL	initFPSmanager = false;

void setFramerateLimit(int fpsLimit)
{
	if (!initFPSmanager)
	{
		/* Initialize framerate handler */
		SDL_initFramerate(&wzFPSmanager);
		initFPSmanager = true;
	}
	SDL_setFramerate(&wzFPSmanager, fpsLimit);
}


int getFramerateLimit(void)
{
	return SDL_getFramerate(&wzFPSmanager);
}

/* InitFrameStuff - needs to be called once before frame loop commences */
static void InitFrameStuff( void )
{
	UDWORD i;

	for (i=0; i<TIMESPAN; i++)
	{
		FrameCounts[i] = IN_A_FRAME;
	}

	FrameIndex = 0;
	curFrames = 0;
	lastFrames = 0;
	curTicks = 0;
	lastTicks = 0;
}

/* MaintainFrameStuff - call this during completion of each frame loop */
static void MaintainFrameStuff( void )
{
	curTicks = SDL_GetTicks();
	curFrames++;

	// Update the framerate only once per second
	if ( curTicks >= lastTicks + 1000 )
	{
		// TODO Would have to be normalized to be correct for < 1 fps:
		// FrameCounts[FrameIndex++] = 1000 * (curFrames - lastFrames) / (curTicks - lastTicks);
		FrameCounts[FrameIndex++] = curFrames - lastFrames;
		if ( FrameIndex >= TIMESPAN )
		{
			FrameIndex = 0;
		}
		lastTicks = curTicks;
		lastFrames = curFrames;
	}
}


UDWORD frameGetAverageRate(void)
{
	SDWORD averageFrames = 0, i = 0;
	for ( i = 0; i < TIMESPAN; i++ )
		averageFrames += FrameCounts[i];
	averageFrames /= TIMESPAN;

	return averageFrames;
}


UDWORD	frameGetFrameNumber(void)
{
	return curFrames;
}


/** Set the current cursor from a Resource ID
 */
void frameSetCursor(CURSOR cur)
{
	ASSERT(cur < CURSOR_MAX, "frameSetCursorFromRes: bad resource ID" );

	//If we are already using this cursor then  return
	if (cur != currentCursor)
        {
		SDL_SetCursor(aCursors[cur]);
		currentCursor = cur;
        }
}


static void initCursors(void)
{
	aCursors[CURSOR_ARROW]       = init_system_cursor(CURSOR_ARROW, cursor_type);
	aCursors[CURSOR_DEST]        = init_system_cursor(CURSOR_DEST, cursor_type);
	aCursors[CURSOR_SIGHT]       = init_system_cursor(CURSOR_SIGHT, cursor_type);
	aCursors[CURSOR_TARGET]      = init_system_cursor(CURSOR_TARGET, cursor_type);
	aCursors[CURSOR_LARROW]      = init_system_cursor(CURSOR_LARROW, cursor_type);
	aCursors[CURSOR_RARROW]      = init_system_cursor(CURSOR_RARROW, cursor_type);
	aCursors[CURSOR_DARROW]      = init_system_cursor(CURSOR_DARROW, cursor_type);
	aCursors[CURSOR_UARROW]      = init_system_cursor(CURSOR_UARROW, cursor_type);
	aCursors[CURSOR_DEFAULT]     = init_system_cursor(CURSOR_DEFAULT, cursor_type);
	aCursors[CURSOR_EDGEOFMAP]   = init_system_cursor(CURSOR_EDGEOFMAP, cursor_type);
	aCursors[CURSOR_ATTACH]      = init_system_cursor(CURSOR_ATTACH, cursor_type);
	aCursors[CURSOR_ATTACK]      = init_system_cursor(CURSOR_ATTACK, cursor_type);
	aCursors[CURSOR_BOMB]        = init_system_cursor(CURSOR_BOMB, cursor_type);
	aCursors[CURSOR_BRIDGE]      = init_system_cursor(CURSOR_BRIDGE, cursor_type);
	aCursors[CURSOR_BUILD]       = init_system_cursor(CURSOR_BUILD, cursor_type);
	aCursors[CURSOR_EMBARK]      = init_system_cursor(CURSOR_EMBARK, cursor_type);
	aCursors[CURSOR_FIX]         = init_system_cursor(CURSOR_FIX, cursor_type);
	aCursors[CURSOR_GUARD]       = init_system_cursor(CURSOR_GUARD, cursor_type);
	aCursors[CURSOR_JAM]         = init_system_cursor(CURSOR_JAM, cursor_type);
	aCursors[CURSOR_LOCKON]      = init_system_cursor(CURSOR_LOCKON, cursor_type);
	aCursors[CURSOR_MENU]        = init_system_cursor(CURSOR_MENU, cursor_type);
	aCursors[CURSOR_MOVE]        = init_system_cursor(CURSOR_MOVE, cursor_type);
	aCursors[CURSOR_NOTPOSSIBLE] = init_system_cursor(CURSOR_NOTPOSSIBLE, cursor_type);
	aCursors[CURSOR_PICKUP]      = init_system_cursor(CURSOR_PICKUP, cursor_type);
	aCursors[CURSOR_SEEKREPAIR]  = init_system_cursor(CURSOR_SEEKREPAIR, cursor_type);
	aCursors[CURSOR_SELECT]      = init_system_cursor(CURSOR_SELECT, cursor_type);
}


static void freeCursors(void)
{
	unsigned int i;
	for(i = 0 ; i < ARRAY_SIZE(aCursors); ++i)
	{
		SDL_FreeCursor(aCursors[i]);
	}
}

/*
 * frameInitialise
 *
 * Initialise the framework library. - PC version
 */
BOOL frameInitialise(
					const char *pWindowName,// The text to appear in the window title bar
					UDWORD width,			// The display width
					UDWORD height,			// The display height
					UDWORD bitDepth,		// The display bit depth
					BOOL fullScreen		// Whether to start full screen or windowed
					)
{
	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0)
	{
		debug( LOG_ERROR, "Error: Could not initialise SDL (%s).\n", SDL_GetError() );
		return false;
	}

	SDL_WM_SetCaption(pWindowName, NULL);

	/* Initialise the trig stuff */
	if (!trigInitialise())
	{
		return false;
	}

	/* initialise all cursors */
	initCursors();

	if (!screenInitialise(width, height, bitDepth, fullScreen))
	{
		return false;
	}

	/* Initialise the input system */
	inputInitialise();

	/* Initialise the frame rate stuff */
	InitFrameStuff();

	// Initialise the resource stuff
	if (!resInitialise())
	{
		return false;
	}

	return true;
}


/*!
 * Call this each cycle to do general house keeping.
 */
void frameUpdate(void)
{
	/* Tell the input system about the start of another frame */
	inputNewFrame();

	/* Update the frame rate stuff */
	MaintainFrameStuff();

	SDL_framerateDelay(&wzFPSmanager);
}


/*!
 * Cleanup framework
 */
void frameShutDown(void)
{
	screenShutDown();

	/* Free all cursors */
	freeCursors();

	/* Destroy the Application window */
	SDL_Quit();

	/* shutdown the trig stuff */
	trigShutDown();

	// Shutdown the resource stuff
	resShutDown();
}

PHYSFS_file* openLoadFile(const char* fileName, bool hard_fail)
{
	PHYSFS_file* fileHandle = PHYSFS_openRead(fileName);
	if (!fileHandle)
	{
		if (hard_fail)
		{
			ASSERT(!"unable to open file", "file %s could not be opened: %s", fileName, PHYSFS_getLastError());
		}
		else
		{
			debug(LOG_WARNING, "optional file %s could not be opened: %s", fileName, PHYSFS_getLastError());
		}
	}

	return fileHandle;
}

/***************************************************************************
  Load the file with name pointed to by pFileName into a memory buffer.
  If AllocateMem is true then the memory is allocated ... else it is
  already in allocated in ppFileData, and the max size is in pFileSize
  ... this is adjusted to the actual loaded file size.

  If hard_fail is true, we will assert and report on failures.
***************************************************************************/
static BOOL loadFile2(const char *pFileName, char **ppFileData, UDWORD *pFileSize,
                      BOOL AllocateMem, BOOL hard_fail)
{
	PHYSFS_file *pfile;
	PHYSFS_sint64 filesize;
	PHYSFS_sint64 length_read;

	pfile = openLoadFile(pFileName, hard_fail);
	if (!pfile)
	{
		return false;
	}

	filesize = PHYSFS_fileLength(pfile);

	//debug(LOG_WZ, "loadFile2: %s opened, size %i", pFileName, filesize);

	if (AllocateMem)
	{
		// Allocate a buffer to store the data and a terminating zero
		*ppFileData = (char*)malloc(filesize + 1);
		if (*ppFileData == NULL)
		{
			debug(LOG_ERROR, "loadFile2: Out of memory loading %s", pFileName);
			assert(false);
			return false;
		}
	}
	else
	{
		if (filesize > *pFileSize)
		{
			debug(LOG_ERROR, "loadFile2: No room for file %s, buffer is too small! Got: %d Need: %ld", pFileName, *pFileSize, (long)filesize);
			assert(false);
			return false;
		}
		assert(*ppFileData != NULL);
	}

	/* Load the file data */
	length_read = PHYSFS_read(pfile, *ppFileData, 1, filesize);
	if (length_read != filesize)
	{
		if (AllocateMem)
		{
			free(*ppFileData);
			*ppFileData = NULL;
		}

		debug(LOG_ERROR, "loadFile2: Reading %s short: %s",
		      pFileName, PHYSFS_getLastError());
		assert(false);
		return false;
	}

	if (!PHYSFS_close(pfile))
	{
		if (AllocateMem)
		{
			free(*ppFileData);
			*ppFileData = NULL;
		}

		debug(LOG_ERROR, "loadFile2: Error closing %s: %s", pFileName,
		      PHYSFS_getLastError());
		assert(false);
		return false;
	}

	// Add the terminating zero
	*((*ppFileData) + filesize) = 0;

	// always set to correct size
	*pFileSize = filesize;

	return true;
}

PHYSFS_file* openSaveFile(const char* fileName)
{
	PHYSFS_file* fileHandle = PHYSFS_openWrite(fileName);
	if (!fileHandle)
	{
		const char *found = PHYSFS_getRealDir(fileName);

		debug(LOG_ERROR, "saveFile: %s could not be opened: %s", fileName, PHYSFS_getLastError());
		if (found)
		{
			debug(LOG_ERROR, "saveFile: %s found as %s", fileName, found);
		}

		assert(!"openSaveFile: couldn't open file for writing");
	}

	return fileHandle;
}

/***************************************************************************
	Save the data in the buffer into the given file.
***************************************************************************/
BOOL saveFile(const char *pFileName, const char *pFileData, UDWORD fileSize)
{
	PHYSFS_file *pfile;
	PHYSFS_uint32 size = fileSize;

	debug(LOG_WZ, "We are to write (%s) of size %d", pFileName, fileSize);
	pfile = openSaveFile(pFileName);
	if (!pfile)
	{
		return false;
	}

	if (PHYSFS_write(pfile, pFileData, 1, size) != size) {
		debug(LOG_ERROR, "saveFile: %s could not write: %s", pFileName,
		      PHYSFS_getLastError());
		assert(false);
		return false;
	}
	if (!PHYSFS_close(pfile)) {
		debug(LOG_ERROR, "saveFile: Error closing %s: %s", pFileName,
		      PHYSFS_getLastError());
		assert(false);
		return false;
	}

	if (PHYSFS_getRealDir(pFileName) == NULL) {
		// weird
		debug(LOG_ERROR, "saveFile: PHYSFS_getRealDir(%s) returns NULL?!",
		      pFileName);
	} else {
	  debug(LOG_WZ, "Successfully wrote to %s%s%s with %d bytes",
		      PHYSFS_getRealDir(pFileName), PHYSFS_getDirSeparator(),
		      pFileName, size);
	}
	return true;
}

BOOL loadFile(const char *pFileName, char **ppFileData, UDWORD *pFileSize)
{
	return loadFile2(pFileName, ppFileData, pFileSize, true, true);
}

// load a file from disk into a fixed memory buffer
BOOL loadFileToBuffer(const char *pFileName, char *pFileBuffer, UDWORD bufferSize, UDWORD *pSize)
{
	*pSize = bufferSize;
	return loadFile2(pFileName, &pFileBuffer, pSize, false, true);
}

// as above but returns quietly if no file found
BOOL loadFileToBufferNoError(const char *pFileName, char *pFileBuffer, UDWORD bufferSize, UDWORD *pSize)
{
	*pSize = bufferSize;
	return loadFile2(pFileName, &pFileBuffer, pSize, false, false);
}


/* next four used in HashPJW */
#define	BITS_IN_int		32
#define	THREE_QUARTERS	((UDWORD) ((BITS_IN_int * 3) / 4))
#define	ONE_EIGHTH		((UDWORD) (BITS_IN_int / 8))
#define	HIGH_BITS		( ~((UDWORD)(~0) >> ONE_EIGHTH ))


/***************************************************************************/
/*
 * HashString
 *
 * Adaptation of Peter Weinberger's (PJW) generic hashing algorithm listed
 * in Binstock+Rex, "Practical Algorithms" p 69.
 *
 * Accepts string and returns hashed integer.
 */
/***************************************************************************/
UDWORD HashString( const char *c )
{
	UDWORD	iHashValue;

	assert(c != NULL);
	assert(*c != 0x0);

	for (iHashValue = 0; *c; ++c)
	{
		unsigned int i;
		iHashValue = ( iHashValue << ONE_EIGHTH ) + *c;

		i = iHashValue & HIGH_BITS;
		if ( i != 0 )
		{
			iHashValue = ( iHashValue ^ ( i >> THREE_QUARTERS ) ) &
							~HIGH_BITS;
		}
	}
	return iHashValue;
}

/* Converts lower case ASCII characters into upper case characters
 * \param c the character to convert
 * \return an upper case ASCII character
 */
static inline char upcaseASCII(char c)
{
	// If this is _not_ a lower case character simply return
	if (c < 'a' || c > 'z')
		return c;
	// Otherwise substract 32 to make the lower case character an upper case one
	else
		return c - 32;
}

UDWORD HashStringIgnoreCase( const char *c )
{
	UDWORD	iHashValue;

	assert(c != NULL);
	assert(*c != 0x0);

	for (iHashValue=0; *c; ++c)
	{
		unsigned int i;
		iHashValue = ( iHashValue << ONE_EIGHTH ) + upcaseASCII(*c);

		i = iHashValue & HIGH_BITS;
		if ( i != 0 )
		{
			iHashValue = ( iHashValue ^ ( i >> THREE_QUARTERS ) ) &
							~HIGH_BITS;
		}
	}
	return iHashValue;
}
